package server;

import java.io.IOException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.util.HashMap;

import javax.swing.JOptionPane;

import messages.SignatureCheckRequestMessage;
import messages.SignatureCheckResponseMessage;
import messages.SignatureRegisterRequestMessage;
import messages.SignatureRegisterResponseMessage;
import security.Hash;
import security.crypto.AssimEncryption;
import security.keyManagement.KeyStoreManager;
import serverPresentation.MainMenu;
import threads.CertcopVericopConnection;
import utils.CurrentDate;
import utils.Log;
import connection.DatabaseConnection;
import connection.exception.ConnectionResetException;
import connection.exception.PropertiesUndefinedException;

public class ServerData {

	private Log _log;
	private Boolean _isOnline;
	private Boolean _isLoggedOn;
	private KeyStoreManager _keyStoreManager;
	private DatabaseConnection _databaseConnection;
	private CertcopVericopConnection _vericop; 
	private MainMenu _mainMenu;
	
	private final Object _loginLock = new Object();
	
	private static ServerData _singletonServerData;

	private HashMap<Integer, CompetenceSignatureComponents> _compNonces;
	
	public ServerData(){
		_singletonServerData = this;
		
		_compNonces = new HashMap<Integer, CompetenceSignatureComponents>();
		_mainMenu = new MainMenu();
		
		_log = new Log("Server");
		_isOnline = true;
		_isLoggedOn = false;				
	}
	
	public boolean init(String password){
		try {
			_keyStoreManager = new KeyStoreManager(password, true, true);
			
			_databaseConnection = new DatabaseConnection(_keyStoreManager, "config/DBConf.properties");
			
			setLoggedOn(true);
			return true;
		} catch (IOException e) {
			JOptionPane.showMessageDialog(null, "Os dados de login introduzidos são incorrectos... Tente novamente!!");
			setLoggedOn(false);
			return false;			
		} catch (PropertiesUndefinedException e) {
			JOptionPane.showMessageDialog(null, "Verifique a localização do ficheiro de Configuração CERTCOPServer!!");
			setLoggedOn(false);
			return false;
		}
	}

	public void writeToLog(String text){
		_log.write(text);
	}

	public boolean isOnline(){
		return _isOnline;
	}

	public void setOffline(){
		_isOnline = false;
	}

	public boolean isLoggedOn() {
		synchronized(_loginLock){
			try {
				_loginLock.wait();

				return _isLoggedOn;
			} catch (InterruptedException e) {
				e.printStackTrace();
				return false;
			}
		}
	}

	public void setLoggedOn(boolean loggedOn){
		synchronized(_loginLock){
			_isLoggedOn = loggedOn;
			_loginLock.notify();
		}
	}
	
	public DatabaseConnection getDatabaseConnection(){
		return _databaseConnection;
	}
	
	public static synchronized ServerData getServerData() {
		if (_singletonServerData == null) {
			return null;
		}
		return _singletonServerData;
	}
	public Object clone() throws CloneNotSupportedException {
		throw new CloneNotSupportedException();
	}

	public KeyStoreManager getKeystoreManager() {
		return _keyStoreManager;
	}

	public void addNonce(Integer nonce, String fName, String lName, String comp) {
        // Update List
        _compNonces.put(nonce, new CompetenceSignatureComponents(fName, lName, comp));
	}
	
	public String[] getUserByNonce(Integer nonce) {
	    CompetenceSignatureComponents csc = _compNonces.get(nonce);
	    String[] result = new String[] {csc.firstName, csc.lastName};
	    
	    return result;
	}

	public void removeNonce(Integer nonce) {
	    _compNonces.remove(nonce);
	}
	
	/**
     * Returns whether or not the given user has the given Competency.
     * 
     * @param firstName First Name of the subject
     * @param lastName Last Name of the subject
     * @param competency Competency to test
     * @return Boolean indicating if the user has the Comeptency
     */
    public Boolean userHasCompetency(String firstName, String lastName, String competency) {
        
        Boolean res = (_databaseConnection.getUserCompetency(firstName, lastName, competency) != null);  
        
        return res;
    }
    
    /**
     * Returns the current Date and Time, as seen by this server.
     * 
     * @return The string representing the current date and time
     */
    public String getCurrentDate() {
        return CurrentDate.getCurrentTime();
    }
    
    /**
     * Returns a serial number, ready to be used.
     * 
     * @return The serial to be used.
     */
    public String getSerial() {
        Integer value = _databaseConnection.getSerial();
        _databaseConnection.setSerial(value + 1);
        
        return String.format("%010d", value);
    }
    
    /**
     * Generates a Document Signature, for the given parameters (May or may not be VERICOP Certified)
     * 
     * @param file String containing the file contents
     * @param name Name of the signee
     * @param comp Competence used in the signature
     * @param isVericop Boolean Representing of the signature was intended to be VERICOP Certified
     * @return A String array that contains both Document Signature Structure (all the parameters) and its signature
     */
    public String[] generateSignature(String file, String name, String comp, Boolean isVericop, Integer duration) {
        
        // Obter Serial para Assinatura
        String serial = getSerial();

        // Obter Data Corrente
        String date = getCurrentDate();
        
        // Obter ID do Certificado do CERTCOP
        String signerID = _keyStoreManager.getCertcopCertSerial(); 
        
        // Calcular Hash de Documento
        String hash = Hash.getStringHash(file);
        
        // Generate Document Certificate Structure
        String certificateStruct = hash + "\n" + serial + "\n" + name + "\n" + comp + 
                                "\n" + signerID + "\n" + isVericop + "\n" + duration + "\n" + date;
        
        // Sign Certificate
        Boolean isCertcop = true;
        String certHash = Hash.getStringHash(certificateStruct);
        PrivateKey privateKey = _keyStoreManager.getServerPrivateKey(isCertcop);
        String certSignature = AssimEncryption.signHash(certHash, privateKey);
        
        if(isVericop) {
            // Add signature to database
            registerVericopSignature(new Integer(serial), certSignature);
        }
        
        // Return the resulting structure and its signature
        String[] res = {certificateStruct, certSignature};
        return res;
    }
    
    /**
     * Matches the given signature against the stored version
     * associated with the given serial.
     * 
     * @param sigReceived The signature to validate
     * @param serial The serial number of the stored signature to validate against.
     * @return The result if whether or not there was a match
     */
    public Boolean isVericopSignatureValid(String sigReceived, Integer serial) {

        Boolean result = false;
        
        try {
            
            // Enviar pedido ao VERICOP
            SignatureCheckRequestMessage req = new SignatureCheckRequestMessage(serial, sigReceived);
            int sessionId = getVericopThread().sendCertcopRequest(req);
            
            // Esperar por resposta
            SignatureCheckResponseMessage res = 
                (SignatureCheckResponseMessage) getVericopThread().receiveCertcopAnswer(sessionId);
            result = res._result;
        } catch (ConnectionResetException e) {
            e.printStackTrace();
        }
        
        return result;
    }
    
    /**
     * Determines whether the supplied signed value matches
     * the value expected.
     * 
     * @param signature Signature supplied by the client (nonce+competence, ciphered
     *                  with the CC PrivateKey).
     * @param nonce The nonce in the signature value. Also used to get the values
     *               to build the signature.
     * @return Whether or not the signature value matches the expected
     * 
     * TESTED
     */
    public Boolean isCompetenceSignatureValid(String signature, Integer nonce) {
        
        // Get the Authentication Components
        CompetenceSignatureComponents csc = _compNonces.get(nonce);
        
        // Get the subject's certificate
        Certificate cert = _databaseConnection.getUserCert(csc.firstName, csc.lastName);

        // Build the original value
        String valueToCheck = nonce + csc.competence;
        
        Boolean result = AssimEncryption.verifyCcSignature(valueToCheck, signature, cert);
        return result;
    }
    
    /**
     * Registers a signature value on the VERICOP system.
     * 
     * @param serial The serial number of the signature archive.
     * @param sig The signature to register.
     */
    public void registerVericopSignature(Integer serial, String sig) {

        try {
            // Enviar mensagem de pedido ao vericop
            SignatureRegisterRequestMessage req = new SignatureRegisterRequestMessage(serial, sig);
            int sessionId = getVericopThread().sendCertcopRequest(req);
            
            // Receber mensagem de Resposta do VERICOP
            SignatureRegisterResponseMessage res = 
                (SignatureRegisterResponseMessage) getVericopThread().receiveCertcopAnswer(sessionId);
            if(!res._result) {
                throw new RuntimeException("Could not register the new signature on the VERICOP server");
            }
        } catch (ConnectionResetException e) {
            e.printStackTrace();
        }
    }

	public void setVericop(CertcopVericopConnection vericop) {
		_vericop = vericop;		
	}
	
	public CertcopVericopConnection getVericopThread(){
		return _vericop;
	}
	
	public MainMenu getMainMenu(){
		return _mainMenu;
	}
}

